﻿using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml.Linq;
using System.Xml.XPath;
using System.Xml;
using System.Xml.Xsl;

namespace PeshiSoft.DbmlUpdater
{
	class Program
	{
		private static string sqlMetalPath;
		static void Main(string[] args)
		{
			Console.WriteLine();
			Console.WriteLine(" DBML Updater v1.0");
			Console.WriteLine(" ------------------------------------------------------------------------------ ");

			if (!CheckWinSDK())
				return;

			var files = Directory.GetFiles(Environment.CurrentDirectory, "*.dbml.xml", SearchOption.AllDirectories);
			foreach (string f in files)
			{
				string dbmlFileName = Path.Combine(Path.GetDirectoryName(f), Path.GetFileNameWithoutExtension(f));
				string dbmlSubFileName = dbmlFileName.Substring(Environment.CurrentDirectory.Length + 1);

				if (dbmlSubFileName.StartsWith("obj\\", StringComparison.CurrentCultureIgnoreCase))
					continue;

				Console.WriteLine(" " + dbmlSubFileName);

				XDocument doc = XDocument.Load(f);
				XElement dbmlOptions = doc.Root.Element("DBML");
				
				bool generateDBML = false;
				
				if (dbmlOptions != null)
					if (dbmlOptions.Attribute("generate") != null)
						generateDBML = bool.Parse(dbmlOptions.Attribute("generate").Value);


				Console.Write("  - Generating DBML file... ");
				if (generateDBML)
				{
					if (File.Exists(dbmlFileName))
						File.Delete(dbmlFileName);

					string connectionString = dbmlOptions.Attribute("connectionString").Value;
					bool views = dbmlOptions.Attribute("views") != null ? bool.Parse(dbmlOptions.Attribute("views").Value) : false;
					bool functions = dbmlOptions.Attribute("functions") != null ? bool.Parse(dbmlOptions.Attribute("functions").Value) : false;
					bool sprocs = dbmlOptions.Attribute("sprocs") != null ? bool.Parse(dbmlOptions.Attribute("sprocs").Value) : false;
					if (!GenerateDBML(dbmlFileName, connectionString, views, functions, sprocs))
						continue;
				}
				else
				{
					Console.WriteLine("Disabled");
				}

				Console.Write("  - Checking DBML file exists... ");
				if (!File.Exists(dbmlFileName))
				{
					Console.WriteLine("Not found");
					continue;
				}
				Console.WriteLine("OK");


				Console.WriteLine("  - Modifing DBML file");
				XmlDocument dbml = new XmlDocument();
				dbml.Load(dbmlFileName);

				XPathNavigator nav = dbml.CreateNavigator();
				XmlNamespaceManager mgr = new XmlNamespaceManager(new NameTable());
				mgr.AddNamespace("", "http://schemas.microsoft.com/linqtosql/dbml/2007");
				mgr.AddNamespace("ns1", "http://schemas.microsoft.com/linqtosql/dbml/2007");


				int i = -1;
				foreach (XElement el in doc.Root.Element("Changes").Elements())
				{
					i++;
					string xpath = el.Attribute("xpath").Value;
					XPathNavigator item = nav.SelectSingleNode(xpath, mgr);
					if (item == null)
					{
						Console.WriteLine("      - [{0}] node not found", i);
						continue;
					}
					XmlElement target = item.UnderlyingObject as XmlElement;
					if (target == null)
					{
						Console.WriteLine("      - [{0}] target not found", i);
						continue;
					}
					if ("edit".Equals(el.Name.LocalName, StringComparison.CurrentCultureIgnoreCase))
					{
						Console.Write("      - [{0}] editing... ", i);
						foreach (XElement attr in el.Elements())
						{
							string name = attr.Attribute("name").Value;
							string val = attr.Attribute("value").Value;
							//Console.WriteLine("    - " + name + " = " + val);
							target.Attributes[name].Value = val;
						}
						Console.WriteLine("OK");
					}
					else if ("delete".Equals(el.Name.LocalName, StringComparison.CurrentCultureIgnoreCase))
					{
						Console.Write("      - [{0}] deleting... ", i);
						target.ParentNode.RemoveChild(target);
						Console.WriteLine("OK");
					}
				}

				dbml.Save(dbmlFileName);


				XElement codeOptions = doc.Root.Element("Code");
				bool generateCode = false;

				if (codeOptions != null)
					if (codeOptions.Attribute("generate") != null)
						generateCode = bool.Parse(codeOptions.Attribute("generate").Value);

				Console.Write("  - Generating Code file... ");
				if (generateCode)
				{
					string lang = codeOptions.Attribute("lang") != null ? codeOptions.Attribute("lang").Value : "cs";
					string ns = codeOptions.Attribute("namespace") != null ? codeOptions.Attribute("namespace").Value : "";
					string context = codeOptions.Attribute("context") != null ? codeOptions.Attribute("context").Value : "";
					string codeFileName = Path.Combine(Path.GetDirectoryName(dbmlFileName), Path.GetFileNameWithoutExtension(dbmlFileName)) + ".designer." + lang;
					
					if (File.Exists(codeFileName))
						File.Delete(codeFileName);
					
					if (!GenerateCode(dbmlFileName, codeFileName,  ns, context))
						continue;
				}
				else
				{
					Console.WriteLine("Disabled");
				}
			}
			Console.WriteLine();
			Console.WriteLine(" ------------------------------------------------------------------------------ ");
			Console.WriteLine("                                                           " + DateTime.Now.ToString("dd.MM.yyyy HH:mm:ss"));

#if DEBUG
			Console.ReadLine();
#endif
		}

		private static bool CheckWinSDK()
		{
			List<string> paths = new List<string>() 
			{ 
				"v7.0\\Bin\\SqlMetal.exe",
				"v7.0\\Bin\\x64\\SqlMetal.exe",
				"v6.0A\\Bin\\SqlMetal.exe",
				"v6.0A\\Bin\\x64\\SqlMetal.exe"
			};

			string programFiles = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "Microsoft SDKs\\Windows\\");

			for (int i = 0; i < paths.Count; i++)
			{
				sqlMetalPath = Path.Combine(programFiles, paths[i]);
				if (File.Exists(sqlMetalPath))
					return true;
			}

			Console.WriteLine("ERROR: SqlMetal.exe not found. Install Windows SDK.");
			return false;
		}

		private static bool GenerateDBML(string dbmlFileName, string connectionString, bool views, bool functions, bool sprocs)
		{
			List<string> args = new List<string>();
			args.Add("/conn:\"" + connectionString + "\"");
			if (views)
				args.Add("/views");
			if (functions)
				args.Add("/functions");
			if (sprocs)
				args.Add("/sprocs");

			args.Add("/pluralize");

			args.Add("/dbml:\"" + dbmlFileName + "\"");

			
			ProcessStartInfo pi = new ProcessStartInfo(sqlMetalPath, string.Join(" ", args.ToArray()));
			pi.CreateNoWindow = true;
			pi.UseShellExecute = false;
			pi.RedirectStandardOutput = true;
			Process p = Process.Start(pi);
			p.WaitForExit();

			if (p.ExitCode == 0)
			{
				Console.WriteLine("OK");
				return true;
			}

			Console.WriteLine("ERROR: " + p.StandardOutput.ReadToEnd());

			return false;
		}

		private static bool GenerateCode(string dbmlFileName, string codeFileName, string ns, string context)
		{
			List<string> args = new List<string>();
			args.Add("/code:\"" + codeFileName + "\"");
			
			if (!string.IsNullOrEmpty(ns))
				args.Add("/namespace:" + ns);
			
			if (!string.IsNullOrEmpty(context))
				args.Add("/context:" + context + "Context");

			args.Add("\"" + dbmlFileName + "\"");

			
			ProcessStartInfo pi = new ProcessStartInfo(sqlMetalPath, string.Join(" ", args.ToArray()));
			pi.CreateNoWindow = true;
			pi.UseShellExecute = false;
			pi.RedirectStandardOutput = true;
			Process p = Process.Start(pi);
			p.WaitForExit();

			if (p.ExitCode == 0)
			{
				Console.WriteLine("OK");
				return true;
			}

			Console.WriteLine("ERROR: " + p.StandardOutput.ReadToEnd());

			return false;
		}
	}
}
